#ifndef __LDX_SMESHBUFFER_H_INCLUDE__
#define __LDX_SMESHBUFFER_H_INCLUDE__
#include "LDXConfig.h"
#include "IMeshBuffer.h"
#include "S3DVertex.h"
#include "CBufferObject.h"
#include "SDeviceMap.h"

namespace ldx
{
	namespace scene
	{
		class SMeshBuffer : public IMeshBuffer
		{
		public:
			//! Default constructor for empty meshbuffer
			SMeshBuffer()
				:ChangedID_Vertex(1),
				ChangedID_Index(1), 
				MappingHint_Vertex(EHM_DEFAULT),
				MappingHint_Index(EHM_DEFAULT)
			{
#ifdef _DEBUG
				setDebugName("SMeshBuffer");
#endif
			}

			~SMeshBuffer()
			{
				if (VertexBufferObj)
					delete VertexBufferObj;

			}

#pragma region ForD3D11BufferObjects
			bool initHardwareBuffers(SDeviceMap dm)
			{
				D3D11_USAGE vUsage =(D3D11_USAGE) this->getHardwareMappingHint_Vertex();
				VertexSize = (mt::uint)Vertices.size();
				VertexStride = sizeof(render::S3DVertex);
				mt::uint vByteWidth = VertexStride*VertexSize;
				VertexBufferObj = new render::CBufferObject(
					dm, vUsage, vByteWidth, D3D11_BIND_VERTEX_BUFFER,0,
					&Vertices[0]);
				D3D11_USAGE iUsage = (D3D11_USAGE) this->getHardwareMappingHint_Index();
				mt::uint offset = 0;
				VertexBufferObj->setVertexBuffers(0, 1, &VertexStride, &offset);
				IndicesSize = static_cast<mt::uint>(Indices.size());
				mt::uint iByteWidth = static_cast<mt::uint>(sizeof(mt::ushort)*IndicesSize);
				IndicesBufferObj = new render::CBufferObject(
					dm, iUsage, iByteWidth, D3D11_BIND_INDEX_BUFFER,
					0,&Indices[0]);
				IndicesBufferObj->setIndexBuffer(DXGI_FORMAT_R16_UINT);
				return true;
			}

			virtual void use()
			{
				mt::uint offset= 0;
				VertexBufferObj->setVertexBuffers(0, 1, &VertexStride,&offset);
				IndicesBufferObj->setIndexBuffer(DXGI_FORMAT_R16_UINT);
			}


#pragma endregion

			//! Get material of this meshbuffer
			/** \return Material of this buffer */
			virtual const render::SMaterial& getMaterial() const
			{
				return Material;
			}


			//! Get material of this meshbuffer
			/** \return Material of this buffer */
			virtual render::SMaterial& getMaterial()
			{
				return Material;
			}


			//! Get pointer to vertices
			/** \return Pointer to vertices. */
			virtual const void* getVertices() const
			{
				return Vertices.data();
			}


			//! Get pointer to vertices
			/** \return Pointer to vertices. */
			virtual void* getVertices()
			{
				return Vertices.data();
			}
			
			//! Get number of vertices
			/** \return Number of vertices. */
			virtual mt::uint getVertexCount() const
			{
				return VertexSize;
			}

			//! Get type of index data which is stored in this meshbuffer.
			/** \return Index type of this buffer. */
			virtual render::E_INDEX_TYPE getIndexType() const
			{
				return render::EIT_16BIT;
			}

			//! Get pointer to indices
			/** \return Pointer to indices. */
			virtual const mt::ushort* getIndices() const
			{
				return Indices.data();
			}


			//! Get pointer to indices
			/** \return Pointer to indices. */
			virtual mt::ushort* getIndices()
			{
				return Indices.data();
			}


			//! Get number of indices
			/** \return Number of indices. */
			virtual mt::uint getIndexCount() const
			{
				return IndicesSize;// static_cast<mt::uint>(Indices.size());
			}


			//! Get the axis aligned bounding box
			/** \return Axis aligned bounding box of this buffer. */
			virtual const mt::aabbox3df& getBoundingBox() const
			{
				return BoundingBox;
			}


			//! Set the axis aligned bounding box
			/** \param box New axis aligned bounding box for this buffer. */
			//! set user axis aligned bounding box
			virtual void setBoundingBox(const mt::aabbox3df& box)
			{
				BoundingBox = box;
			}


			//! Recalculate the bounding box.
			/** should be called if the mesh changed. */
			virtual void recalculateBoundingBox()
			{
				if (Vertices.empty())
					BoundingBox.reset(0, 0, 0);
				else
				{
					BoundingBox.reset(Vertices[0].Pos);
					for (mt::uint i = 1; i<Vertices.size(); ++i)
						BoundingBox.addInternalPoint(Vertices[i].Pos);
				}
			}


			//! Get type of vertex data stored in this buffer.
			/** \return Type of vertex data. */
			virtual render::E_VERTEX_TYPE getVertexType() const
			{
				return render::S3DVertex().getType();
			}

			//! returns position of vertex i
			virtual const mt::vec3f& getPosition(mt::uint i) const
			{
				return Vertices[i].Pos;
			}

			//! returns position of vertex i
			virtual mt::vec3f& getPosition(mt::uint i)
			{
				return Vertices[i].Pos;
			}

			//! returns normal of vertex i
			virtual const mt::vec3f& getNormal(mt::uint i) const
			{
				return Vertices[i].Normal;
			}

			//! returns normal of vertex i
			virtual mt::vec3f& getNormal(mt::uint i)
			{
				return Vertices[i].Normal;
			}

			//! returns texture coord of vertex i
			virtual const mt::vector2df& getTCoords(mt::uint i) const
			{
				return Vertices[i].TCoords;
			}

			//! returns texture coord of vertex i
			virtual mt::vector2df& getTCoords(mt::uint i)
			{
				return Vertices[i].TCoords;
			}


			//! Append the vertices and indices to the current buffer
			/** Only works for compatible types, i.e. either the same type
			or the main buffer is of standard type. Otherwise, behavior is
			undefined.
			*/
			virtual void append(const void* const vertices, mt::uint numVertices, const mt::ushort* const indices, mt::uint numIndices)
			{
				if (vertices == getVertices())
					return;

				const mt::uint vertexCount = getVertexCount();
				mt::uint i;
				Vertices.resize(vertexCount + numVertices);
				//Vertices.reallocate(vertexCount + numVertices);
				for (i = 0; i<numVertices; ++i)
				{
					Vertices.push_back(reinterpret_cast<const render::S3DVertex*>(vertices)[i]);
					BoundingBox.addInternalPoint(reinterpret_cast<const render::S3DVertex*>(vertices)[i].Pos);
				}

				Indices.resize(getIndexCount() + numIndices);
				for (i = 0; i<numIndices; ++i)
				{
					Indices.push_back(indices[i] + vertexCount);
				}
			}


			//! Append the meshbuffer to the current buffer
			/** Only works for compatible types, i.e. either the same type
			or the main buffer is of standard type. Otherwise, behavior is
			undefined.
			\param other Meshbuffer to be appended to this one.
			*/
			virtual void append(const IMeshBuffer* const other)
			{
				/*
				if (this==other)
				return;

				const mt::uint vertexCount = getVertexCount();
				mt::uint i;

				Vertices.reallocate(vertexCount+other->getVertexCount());
				for (i=0; i<other->getVertexCount(); ++i)
				{
				Vertices.push_back(reinterpret_cast<const S3DVertex*>(other->getVertices())[i]);
				}

				Indices.reallocate(getIndexCount()+other->getIndexCount());
				for (i=0; i<other->getIndexCount(); ++i)
				{
				Indices.push_back(other->getIndices()[i]+vertexCount);
				}
				BoundingBox.addInternalBox(other->getBoundingBox());
				*/
			}


			//! get the current hardware mapping hint
			virtual E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const
			{
				return MappingHint_Vertex;
			}

			//! get the current hardware mapping hint
			virtual E_HARDWARE_MAPPING getHardwareMappingHint_Index() const
			{
				return MappingHint_Index;
			}

			//! set the hardware mapping hint, for driver
			virtual void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint, E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX)
			{
				if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX)
					MappingHint_Vertex = NewMappingHint;
				if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX)
					MappingHint_Index = NewMappingHint;
			}


			//! flags the mesh as changed, reloads hardware buffers
			virtual void setDirty(E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX)
			{
				if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX)
					++ChangedID_Vertex;
				if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX)
					++ChangedID_Index;
			}

			//! Get the currently used ID for identification of changes.
			/** This shouldn't be used for anything outside the VideoDriver. */
			virtual mt::uint getChangedID_Vertex() const { return ChangedID_Vertex; }

			//! Get the currently used ID for identification of changes.
			/** This shouldn't be used for anything outside the VideoDriver. */
			virtual mt::uint getChangedID_Index() const { return ChangedID_Index; }

			mt::uint ChangedID_Vertex;
			mt::uint ChangedID_Index;

			//! hardware mapping hint
			E_HARDWARE_MAPPING MappingHint_Vertex;
			E_HARDWARE_MAPPING MappingHint_Index;

			render::CBufferObject* VertexBufferObj;
			render::CBufferObject* IndicesBufferObj;

			//! Material for this meshbuffer.
			render::SMaterial Material;
			//! Vertices of this buffer
			std::vector<render::S3DVertex> Vertices;
			//! Indices into the vertices of this buffer.
			std::vector<mt::ushort> Indices;
			mt::uint IndicesSize;
			mt::uint VertexSize;
			mt::uint VertexStride;

			//! Bounding box of this meshbuffer.
			mt::aabbox3df BoundingBox;
		};
	}

}
#endif